package gzran

import (
	"compress/flate"
	"compress/gzip"
	"io"
)

const (
	huffmanChunkBits  = 9
	huffmanNumChunks  = 1 << huffmanChunkBits
	huffmanCountMask  = 15
	huffmanValueShift = 4
)

const (
	maxCodeLen = 16 // max length of Huffman code
	// The next three numbers come from the RFC section 3.2.7, with the
	// additional proviso in section 3.2.5 which implies that distance codes
	// 30 and 31 should never occur in compressed data.
	maxNumLit  = 286
	maxNumDist = 30
	numCodes   = 19 // number of codes in Huffman meta-code
)

type huffmanDecoder struct {
	min      int                      // the minimum code length
	chunks   [huffmanNumChunks]uint32 // chunks as described above
	links    [][]uint32               // overflow links
	linkMask uint32                   // mask the width of the link table
}
type DictDecoder struct {
	Hist []byte // Sliding window history
	// Invariant: 0 <= rdPos <= wrPos <= len(hist)
	WrPos int  // Current output position in buffer
	RdPos int  // Have emitted hist[:rdPos] already
	Full  bool // Has a full window length been written yet?
}

func (dd *DictDecoder) readFlush() []byte {
	toRead := dd.Hist[dd.RdPos:dd.WrPos]
	dd.RdPos = dd.WrPos
	if dd.WrPos == len(dd.Hist) {
		dd.WrPos, dd.RdPos = 0, 0
		dd.Full = true
	}
	return toRead
}

type reader struct {
	gzip.Header  // valid after NewReader or Reader.Reset
	r            flate.Reader
	decompressor io.ReadCloser
}

type decompressor struct {
	// Input source.
	r       flate.Reader
	roffset int64

	// Input bits, in top of b.
	b  uint32
	nb uint

	// Huffman decoders for literal/length, distance.
	h1, h2 huffmanDecoder

	// Length arrays used to define Huffman codes.
	bits     *[maxNumLit + maxNumDist]int
	codebits *[numCodes]int

	// Output history, buffer.
	dict DictDecoder

	// Temporary buffer (avoids repeated allocation).
	buf [4]byte

	// Next step in the decompression,
	// and decompression state.
	step      func(*decompressor)
	stepState int
	final     bool
	err       error
	toRead    []byte
	hl, hd    *huffmanDecoder
	copyLen   int
	copyDist  int
}
